package com.wsoft.core.config;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.wsoft.constant.RedisKeyConstant;
import com.wsoft.core.annotation.GetRedisCache;
import com.wsoft.core.annotation.InitRedisCache;
import com.wsoft.core.exception.Asserts;
import com.wsoft.core.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.stream.Collectors;

/**
 * 缓存切面
 *
 * @Author zhonglj
 * @Date 2022/9/7 18:47
 */
@Slf4j
@Component
@Aspect
public class RedisCacheAspect {
    @Resource
    private RedisService redisService;

    @Pointcut("@annotation(com.wsoft.core.annotation.GetRedisCache)")
    public void getRedisCache() {
    }

    @Pointcut("@annotation(com.wsoft.core.annotation.InitRedisCache)")
    public void initRedisCache() {
    }

    @Around("getRedisCache()")
    public Object handle(ProceedingJoinPoint joinPoint) throws Throwable {
        GetRedisCache annotation = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(GetRedisCache.class);
        String redisKey = getRedisKey(joinPoint, annotation.type(), annotation.value());
        // 尝试从缓存获取数据
        Object val = redisService.get(redisKey);
        if (val != null) {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Class returnType = method.getReturnType();

            if (val instanceof Collection) {
                // 获取返回列表泛型
                Type actualReturnType = ((ParameterizedTypeImpl) method.getGenericReturnType()).getActualTypeArguments()[0];
                Class<?> actualReturnClass = Class.forName(actualReturnType.getTypeName());
                return JSONUtil.toList(JSONUtil.parseArray(val), actualReturnClass);
            }

            return Convert.convert(returnType, val);
        }
        Object result = joinPoint.proceed();
        //如果值为null,则不缓存
        if (ObjectUtil.isEmpty(result)) {
            return result;
        }
        // 缓存执行数据到redis
        Long expire = Long.valueOf(annotation.expire_sec());
        if (expire > 0) {
            redisService.set(redisKey, result, expire);
        } else {
            redisService.set(redisKey, result);
        }
        return result;
    }

    /**
     * 无异常执行完成，需要更新缓存
     *
     * @param
     * @return void
     **/
    @Around("initRedisCache()")
    public Object afterUpdateData(ProceedingJoinPoint joinPoint) throws Throwable {
        Object returnVal = joinPoint.proceed();
        // 初始化缓存数据
        InitRedisCache annotation = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(InitRedisCache.class);

        String type = annotation.type();
        String value = annotation.value();
        String[] types = annotation.types();
        String[] values = annotation.values();

        if (StrUtil.isNotBlank(type)) {
            delKey(joinPoint, type, value);
        }

        if (ArrayUtil.isNotEmpty(types)) {
            for (int i = 0; i < types.length; i++) {
                String oneValue = "";
                if (ArrayUtil.isNotEmpty(values) && values.length >= i) {
                    oneValue = values[i];
                }
                delKey(joinPoint, types[i], oneValue);
            }
        }

        return returnVal;
    }

    private void delKey(ProceedingJoinPoint joinPoint, String type, String value) {
        if (StrUtil.equals(RedisKeyConstant.REDIS_PATTERN, value)) {
            redisService.deleteByKeyPattern(getRedisKey(joinPoint, type, value));
        } else {
            redisService.del(getRedisKey(joinPoint, type, value));
        }
    }


    /**
     * 根据函数参数生成redis key治
     *
     * @param joinPoint
     * @param type      缓存类型
     * @return java.lang.String
     **/
    private String getRedisKey(ProceedingJoinPoint joinPoint, String type, String value) {
        if (StrUtil.isBlank(type) || value == null) {
            Asserts.fail("生成redisCache,type与value不能为空!");
        }
        String methodName = joinPoint.getSignature().getName();

        if (value == null) {
            return getRedisKeyExp(joinPoint, type);
        }
        //key加上变量
        if (StringUtils.hasText(value) && !RedisKeyConstant.REDIS_NULL.equals(value) &&!RedisKeyConstant.REDIS_PATTERN.equals(value)) {
            ArrayList<Object> paramValS = CollUtil.newArrayList(joinPoint.getArgs());
            if (ArrayUtil.isEmpty(paramValS)) {
                Asserts.fail("缓存注解时，必须有参数!");
            }
            Object obj = paramValS.get(0);
            if (RedisKeyConstant.REDIS_SELF.equals(value)) {
                return StrUtil.format("{}{}", type, obj.toString());
            }
            try {
                String[] point = value.split("\\.");
                for (String fieldName : point) {
                    obj = getObjectField(obj, fieldName);
                }
                return StrUtil.format("{}{}", type, obj.toString());
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                Asserts.fail("参数中无此属性!");
            }
        }
        return type;
    }

    private Object getObjectField(Object object, String fieldName) throws NoSuchFieldException, IllegalAccessException {
        Field field = object.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        return field.get(object);
    }

    private String getRedisKeyExp(ProceedingJoinPoint joinPoint, String type) {
        if (StrUtil.isBlank(type)) {
            Asserts.fail("生成redisCache,type不能为空!");
        }
        String methodName = joinPoint.getSignature().getName();
        ArrayList<Object> paramValS = CollUtil.newArrayList(joinPoint.getArgs());
        // 生成缓存key
        String paramsKey = paramValS.stream().map(e -> String.valueOf(e)).collect(Collectors.joining("_"));
        return StrUtil.format("{}:{}", type, paramsKey);
    }
}
